NUMA架构下的内存分配策略
NUMA架构下的内存分配策略
通常的内存分配策略我们已经有所了解了。UMA是统一内存访问,无论任何 CPU 执行操作,访问时间都相等。而NUMA是非统一内存访问,在不同的节点的访问速度不一样,离本地越远访问越慢。在NUMA上我们有不只一个节点,我们可以在不同的节点上分配内存。
内存分配和策略
通常NUMA下的内存分配有显式和隐式两种。
1. 显式分配
显式分配即指定节点的分配函数,此类基础分配函数主要有2个:Buddy系统的 alloc_pages_node()和SLAB系统的kmem_cache_alloc_node(),其它的函数都可以从这2个派生出来。
例如,kmalloc_node()最终调用kmem_cache_alloc_node()进行分配。
1. Buddy显式分配
alloc_pages_node()分配流程:
1 | alloc_pages_node |
如果节点的nid为NUMA_NO_NODE(非 NUMA 系统),就在当前cpu运行的节点上进行分配,否则就在指定的nid上分配,最终执行伙伴系统的核心分配函数__alloc_pages_nodemask。
如果节点的nid为NUMA_NO_NODE(非 NUMA 系统),就在当前cpu运行的节点上进行分配,否则就在指定的nid上分配,最终执行伙伴系统的核心分配函数__alloc_pages_nodemask。
2. SLAB显式分配
kmem_cache_alloc_node()分配流程:
1 | kmem_cache_alloc_node |
调用slab_alloc来分配一个新的slab并取出一个node,如果内存分配不成功就转到隐式分配。
2. 隐式分配
配置CONFIG_NUMA后,设备会关联一个NUMA节点信息,struct device结构中会多一个numa_node字段记录本设备所在的节点。
隐式分配即不指定节点的分配函数,此类基础分配函数主要有2个:Buddy系统的 alloc_pages()和SLAB系统的kmem_cache_alloc(),其它的函数都可以从这2个派生出来。
隐式分配涉及到NUMA内存策略(Memory Policy),内核定义了六种内存策略。
1. 内存策略
1 | enum { |
系统的默认分配策略是本地分配(即Local Allocation),但是在启动过程中使用交替策略,这样内核的数据结构是分布在所有Node上的,从而避免启动核所在Node负载过重。一旦系统第一个进程(init)开始执行,策略就变成了本地分配。
系统的默认分配策略是本地分配(即Local Allocation),但是在启动过程中使用交替策略,这样内核的数据结构是分布在所有Node上的,从而避免启动核所在Node负载过重。一旦系统第一个进程(init)开始执行,策略就变成了本地分配。
2. Buddy隐式分配
以默认的NUMA内存策略为例讲解,alloc_pages()分配流程:
1 | alloc_pages |
alloc_pages会交由alloc_pages_current做处理,如果是交错策略(MPOL_INTERLEAVE)就调用alloc_page_interleave进行分配。否则就调用__alloc_pages_nodemask做分配处理。
3. SLAB隐式分配
以默认的NUMA内存策略为例讲解,kmem_cache_alloc(cachep, gfp_flags)分配流程:
1 | kmem_cache_alloc |
前面在slab显式分配的时候就已经介绍过了这部分的函数走向。调用____cache_alloc的cpu_cache_get从本地CPU高速缓存结构中获取对象,如果本地没有合适的就执行cache_alloc_refill,获取node共享高速缓存,如果有就填充,如果不行就从slabs_partial(部分分配的slab)和slabs_free(空的slab)上获取空闲对象。如果还不行就去上级的伙伴系统中分配。如果当前节点上已经没有空闲内存了,就调用____cache_alloc_node定位到别的节点。然后调用get_first_slab从 slab 中获取一个页,如果没有空闲页,就调用cache_grow_begin增加一个可用的slab,它是通过伙伴系统来获取的。